/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.core;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Abstract of IdentityMap.
 * 
 * <p>
 * IdentityMap is defined to support duplicated keys in Map.
 * </p>
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 27 Jun, 2014
 */
@SuppressWarnings("unchecked")
public abstract class AbstractIdentityMap<K, V> implements Map<K, V>,
		Cloneable, Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 7659844412615032043L;

	/**
	 * Sole constructor. (For invocation by subclass constructors, typically
	 * implicit.)
	 */
	protected AbstractIdentityMap() {
	}

	// Query Operations

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation returns <tt>entrySet().size()</tt>.
	 */
	public int size() {
		return entrySet().size();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation returns <tt>size() == 0</tt>.
	 */
	public boolean isEmpty() {
		return size() == 0;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation iterates over <tt>entrySet()</tt> searching for an
	 * entry with the specified value. If such an entry is found, <tt>true</tt>
	 * is returned. If the iteration terminates without finding such an entry,
	 * <tt>false</tt> is returned. Note that this implementation requires linear
	 * time in the size of the map.
	 * 
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 */
	public boolean containsValue(Object value) {
		Iterator<Entry<K, V>> i = entrySet().iterator();
		while (i.hasNext()) {
			Entry<K, V> e = i.next();
			if (value == e.getValue())
				return true;
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation iterates over <tt>entrySet()</tt> searching for an
	 * entry with the specified key. If such an entry is found, <tt>true</tt> is
	 * returned. If the iteration terminates without finding such an entry,
	 * <tt>false</tt> is returned. Note that this implementation requires linear
	 * time in the size of the map; many implementations will override this
	 * method.
	 * 
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 */
	public boolean containsKey(Object key) {
		return containsKey(key, false);
	}

	public boolean containsKey(Object key, boolean withEquals) {
		Iterator<Map.Entry<K, V>> i = entrySet().iterator();
		while (i.hasNext()) {
			Entry<K, V> e = i.next();
			if (key == e.getKey())
				return true;
			if (withEquals && key != null) {
				if (key.equals(e.getKey()))
					return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation iterates over <tt>entrySet()</tt> searching for an
	 * entry with the specified key. If such an entry is found, the entry's
	 * value is returned. If the iteration terminates without finding such an
	 * entry, <tt>null</tt> is returned. Note that this implementation requires
	 * linear time in the size of the map; many implementations will override
	 * this method.
	 * 
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 */
	public V get(Object key) {
		List<V> values = get(key, false);
		return values.size() > 0 ? values.get(0) : null;
	}

	/**
	 * A key getHttpRequest method for Identity Map.
	 * <p>It not only supports hash code compare but also supports comparison by equals method</p>
	 * 
	 * @param key
	 * 		Map key
	 * @param withEquals
	 * 		If true, will support comparison by equals method.
	 * @return
	 * 		A list of values found by Map key
	 */
	public List<V> get(Object key, boolean withEquals) {
		Iterator<Entry<K, V>> i = entrySet().iterator();
		List<V> values = new ArrayList<V>();
		while (i.hasNext()) {
			Entry<K, V> e = i.next();
			if (key == e.getKey()) {
				values.add(e.getValue());
				continue;
			}
			if (withEquals && key != null) {
				if (key.equals(e.getKey()))
					values.add(e.getValue());
			}
		}
		return values;
	}

	// Modification Operations

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation always throws an
	 * <tt>UnsupportedOperationException</tt>.
	 * 
	 * @throws UnsupportedOperationException
	 *             {@inheritDoc}
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 * @throws IllegalArgumentException
	 *             {@inheritDoc}
	 */
	public V put(K key, V value) {
		throw new UnsupportedOperationException();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation iterates over <tt>entrySet()</tt> searching for an
	 * entry with the specified key. If such an entry is found, its value is
	 * obtained with its <tt>getValue</tt> operation, the entry is removed from
	 * the collection (and the backing map) with the iterator's <tt>remove</tt>
	 * operation, and the saved value is returned. If the iteration terminates
	 * without finding such an entry, <tt>null</tt> is returned. Note that this
	 * implementation requires linear time in the size of the map; many
	 * implementations will override this method.
	 * 
	 * <p>
	 * Note that this implementation throws an
	 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> iterator
	 * does not support the <tt>remove</tt> method and this map contains a
	 * mapping for the specified key.
	 * 
	 * @throws UnsupportedOperationException
	 *             {@inheritDoc}
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 */
	public V remove(Object key) {
		List<V> values = remove(key, false);
		return values.size() > 0 ? values.get(0) : null;
	}

	/**
	 * A key remove method for Identity Map.
	 * <p>It not only supports hash code compare but also supports comparison by equals method</p>
	 * 
	 * @param key
	 * 		Map key
	 * @param withEquals
	 * 		If true, will support comparison by equals method.
	 * @return
	 * 		A list of values removed by Map key
	 */
	public List<V> remove(Object key, boolean withEquals) {
		Iterator<Entry<K, V>> i = entrySet().iterator();
		List<V> values = new ArrayList<V>();
		while (i.hasNext()) {
			Entry<K, V> e = i.next();
			if (key == e.getKey()) {
				values.add(e.getValue());
				i.remove();
				continue;
			}
			if (withEquals && key != null) {
				if (key.equals(e.getKey())) {
					values.add(e.getValue());
					i.remove();
				}
			}
		}
		return values;
	}

	// Bulk Operations

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation iterates over the specified map's <tt>entrySet()</tt>
	 * collection, and calls this map's <tt>put</tt> operation once for each
	 * entry returned by the iteration.
	 * 
	 * <p>
	 * Note that this implementation throws an
	 * <tt>UnsupportedOperationException</tt> if this map does not support the
	 * <tt>put</tt> operation and the specified map is nonempty.
	 * 
	 * @throws UnsupportedOperationException
	 *             {@inheritDoc}
	 * @throws ClassCastException
	 *             {@inheritDoc}
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 * @throws IllegalArgumentException
	 *             {@inheritDoc}
	 */
	public void putAll(Map<? extends K, ? extends V> m) {
		for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
			put(e.getKey(), e.getValue());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation calls <tt>entrySet().clear()</tt>.
	 * 
	 * <p>
	 * Note that this implementation throws an
	 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> does not
	 * support the <tt>clear</tt> operation.
	 * 
	 * @throws UnsupportedOperationException
	 *             {@inheritDoc}
	 */
	public void clear() {
		entrySet().clear();
	}

	// Views

	/**
	 * Each of these fields are initialized to contain an instance of the
	 * appropriate view the first time this view is requested. The views are
	 * stateless, so there's no reason to create more than one of each.
	 */
	protected transient volatile Set<K> keySet = null;
	protected transient volatile Collection<V> values = null;

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation returns a setHeader that subclasses {@link AbstractSet}.
	 * The subclass's iterator method returns a "wrapper object" over this map's
	 * <tt>entrySet()</tt> iterator. The <tt>size</tt> method delegates to this
	 * map's <tt>size</tt> method and the <tt>contains</tt> method delegates to
	 * this map's <tt>containsKey</tt> method.
	 * 
	 * <p>
	 * The setHeader is created the first time this method is called, and returned in
	 * response to all subsequent calls. No synchronization is performed, so
	 * there is a slight chance that multiple calls to this method will not all
	 * return the same setHeader.
	 */
	public Set<K> keySet() {
		if (keySet == null) {
			keySet = new AbstractSet<K>() {
				public Iterator<K> iterator() {
					return new Iterator<K>() {
						private Iterator<Entry<K, V>> i = entrySet().iterator();

						public boolean hasNext() {
							return i.hasNext();
						}

						public K next() {
							return i.next().getKey();
						}

						public void remove() {
							i.remove();
						}
					};
				}

				public int size() {
					return AbstractIdentityMap.this.size();
				}

				public boolean isEmpty() {
					return AbstractIdentityMap.this.isEmpty();
				}

				public void clear() {
					AbstractIdentityMap.this.clear();
				}

				public boolean contains(Object k) {
					return AbstractIdentityMap.this.containsKey(k);
				}
			};
		}
		return keySet;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>
	 * This implementation returns a collection that subclasses
	 * {@link AbstractCollection}. The subclass's iterator method returns a
	 * "wrapper object" over this map's <tt>entrySet()</tt> iterator. The
	 * <tt>size</tt> method delegates to this map's <tt>size</tt> method and the
	 * <tt>contains</tt> method delegates to this map's <tt>containsValue</tt>
	 * method.
	 * 
	 * <p>
	 * The collection is created the first time this method is called, and
	 * returned in response to all subsequent calls. No synchronization is
	 * performed, so there is a slight chance that multiple calls to this method
	 * will not all return the same collection.
	 */
	public Collection<V> values() {
		if (values == null) {
			values = new AbstractCollection<V>() {
				public Iterator<V> iterator() {
					return new Iterator<V>() {
						private Iterator<Entry<K, V>> i = entrySet().iterator();

						public boolean hasNext() {
							return i.hasNext();
						}

						public V next() {
							return i.next().getValue();
						}

						public void remove() {
							i.remove();
						}
					};
				}

				public int size() {
					return AbstractIdentityMap.this.size();
				}

				public boolean isEmpty() {
					return AbstractIdentityMap.this.isEmpty();
				}

				public void clear() {
					AbstractIdentityMap.this.clear();
				}

				public boolean contains(Object v) {
					return AbstractIdentityMap.this.containsValue(v);
				}
			};
		}
		return values;
	}

	public abstract Set<Entry<K, V>> entrySet();

	// Comparison and hashing

	/**
	 * Compares the specified object with this map for equality. Returns
	 * <tt>true</tt> if the given object is also a map and the two maps
	 * represent the same mappings. More formally, two maps <tt>m1</tt> and
	 * <tt>m2</tt> represent the same mappings if
	 * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the
	 * <tt>equals</tt> method works properly across different implementations of
	 * the <tt>Map</tt> interface.
	 * 
	 * <p>
	 * This implementation first checks if the specified object is this map; if
	 * so it returns <tt>true</tt>. Then, it checks if the specified object is a
	 * map whose size is identical to the size of this map; if not, it returns
	 * <tt>false</tt>. If so, it iterates over this map's <tt>entrySet</tt>
	 * collection, and checks that the specified map contains each mapping that
	 * this map contains. If the specified map fails to contain such a mapping,
	 * <tt>false</tt> is returned. If the iteration completes, <tt>true</tt> is
	 * returned.
	 * 
	 * @param o
	 *            object to be compared for equality with this map
	 * @return <tt>true</tt> if the specified object is equal to this map
	 */
	public boolean equals(Object o) {
		return equals(o, false);
	}

	/**
	 * A key equals method for Identity Map.
	 * <p>It not only supports hash code compare but also supports comparison by equals method</p>
	 * 
	 * @param key
	 * 		Map key
	 * @param withEquals
	 * 		If true, will support comparison by equals method.
	 * @return
	 * 		If the comparison is equivalent.
	 */
	public boolean equals(Object o, boolean withEquals) {
		if (o == this)
			return true;

		if (!(o instanceof Map))
			return false;
		Map<K, V> m = (Map<K, V>) o;
		if (m.size() != size())
			return false;

		try {
			Iterator<Entry<K, V>> i = entrySet().iterator();
			while (i.hasNext()) {
				Entry<K, V> e = i.next();
				K key = e.getKey();
				V value = e.getValue();
				if (value == null) {
					if (!(m.get(key) == null && m.containsKey(key)))
						return false;
				} else {
					if (withEquals) {
						if (!value.equals(m.get(key)))
							return false;
					} else {
						if (value != m.get(key))
							return false;
					}
				}
			}
		} catch (ClassCastException unused) {
			return false;
		} catch (NullPointerException unused) {
			return false;
		}

		return true;
	}

	/**
	 * Returns the hash code value for this map. The hash code of a map is
	 * defined to be the sum of the hash codes of each entry in the map's
	 * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt>
	 * implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps
	 * <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of
	 * {@link Object#hashCode}.
	 * 
	 * <p>
	 * This implementation iterates over <tt>entrySet()</tt>, calling
	 * {@link Map.Entry#hashCode hashCode()} on each element (entry) in the setHeader,
	 * and adding up the results.
	 * 
	 * @return the hash code value for this map
	 * @see Map.Entry#hashCode()
	 * @see Object#equals(Object)
	 * @see Set#equals(Object)
	 */
	public int hashCode() {
		int h = 0;
		Iterator<Entry<K, V>> i = entrySet().iterator();
		while (i.hasNext())
			h += i.next().hashCode();
		return h;
	}

	/**
	 * Returns a string representation of this map. The string representation
	 * consists of a list of key-value mappings in the order returned by the
	 * map's <tt>entrySet</tt> view's iterator, enclosed in braces (
	 * <tt>"{}"</tt>). Adjacent mappings are separated by the characters
	 * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
	 * the key followed by an equals sign (<tt>"="</tt>) followed by the
	 * associated value. Keys and values are converted to Strings as by
	 * {@link String#valueOf(Object)}.
	 * 
	 * @return a string representation of this map
	 */
	public String toString() {
		Iterator<Entry<K, V>> i = entrySet().iterator();
		if (!i.hasNext())
			return "{}";

		StringBuilder sb = new StringBuilder();
		sb.append('{');
		for (;;) {
			Entry<K, V> e = i.next();
			K key = e.getKey();
			V value = e.getValue();
			sb.append(key == this ? "(this Map)" : key);
			sb.append('=');
			sb.append(value == this ? "(this Map)" : value);
			if (!i.hasNext())
				return sb.append('}').toString();
			sb.append(',').append(' ');
		}
	}

	/**
	 * Returns a shallow copy of this <tt>AbstractMap</tt> instance: the keys
	 * and values themselves are not cloned.
	 * 
	 * @return a shallow copy of this map
	 */
	protected Object clone() throws CloneNotSupportedException {
		AbstractIdentityMap<K, V> result = (AbstractIdentityMap<K, V>) super
				.clone();
		result.keySet = null;
		result.values = null;
		return result;
	}

}